Skip to content

MDEV-40180 Cache constant args in JSON_EQUALS and JSON_OVERLAPS - #5455

Open
VasuBhakt wants to merge 1 commit into
MariaDB:10.11from
VasuBhakt:MDEV-40180-compare-nested-object
Open

MDEV-40180 Cache constant args in JSON_EQUALS and JSON_OVERLAPS#5455
VasuBhakt wants to merge 1 commit into
MariaDB:10.11from
VasuBhakt:MDEV-40180-compare-nested-object

Conversation

@VasuBhakt

Copy link
Copy Markdown

Fixes MDEV-40180

Problem

JSON_EQUALS evaluates and parses constant arguments per-row. JSON_OVERLAPS caches constant arguments in fix_length_and_dec, which fails to recalculate during --ps-protocol executions with changing bound parameters.

Solution

Migrated constant argument caching logic to ::val_bool() for both functions:

  1. Item_func_json_equals: Added deep-copy caching of the normalized DYNAMIC_STRING upon first evaluation of a constant argument. Subsequent evaluations bypass json_normalize_engine and reuse the cached string.
  2. Item_func_json_overlaps: Removed flawed a2_constant caching from fix_length_and_dec and implemented per-execution caching of the evaluated string inside val_bool().
  3. Prepared Statements: Overrode cleanup() in both classes to reset boolean caching flags, ensuring correct cache invalidation between executions.

(Note: The nested object short-circuit evaluation will be addressed in a follow-up incremental PR).

Tests

Executed the json, json_equals, and json_normalize MTR suites (both standard and --ps-protocol) to verify behavior preservation and resolution of the prepared statement cache bug.

Version

The change is directed at versions 10.11, 11.4, 11.8, and 12.3. The PR is based on branch 10.11, being the lowest affected branch.

@MariaDB MariaDB deleted a comment from gemini-code-assist Bot Jul 27, 2026
@grooverdan
grooverdan self-requested a review July 27, 2026 22:39
* Optimize JSON_EQUALS by maintaining DYNAMIC_STRING class
  members to cache constant arguments natively within
  val_bool(), preventing redundant re-parsing and eliminating
  memory allocation overhead by reusing the buffers across
  rows.
* Fix JSON_OVERLAPS constant caching, which was improperly cached in
  fix_length_and_dec (causing --ps-protocol failures), by moving it to
  val_bool().
* Ensure cleanup() correctly resets caching state for both functions
  for prepared statements.
* Note: This commit intentionally skips the nested objects short-circuit
  logic, which will be implemented incrementally in a future update.

Signed-off-by: VasuBhakt <cpswastik31@gmail.com>
@VasuBhakt
VasuBhakt force-pushed the MDEV-40180-compare-nested-object branch from 1a7bfa7 to d137c8b Compare July 30, 2026 17:46

@grooverdan grooverdan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't get time to look at overlaps properly.

with all faults of json_equals - needs test case of an actual table with null/nonnul and constant comparison like

https://github.com/MariaDB/server/pull/5420/changes#diff-1905b4d26db3e2b6344ebfcd668c479351e8ab90301e42a91da2bdbdf7d20afeR4405-R4410

Comment thread sql/item_jsonfunc.cc
}
else
{
a= args[0]->val_json(&a_tmp);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note after next rebase the null checking of a needs to be after this. 63d779c by @vuvova corrected my mistake (thank you!), lets not regress it.

Comment thread sql/item_jsonfunc.cc
dynstr_free(&a_res);
null_value= 1;
return 1;
if(a_null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space after if

Comment thread sql/item_jsonfunc.cc

if (!cached_a.str)
{
if (init_dynamic_string(&cached_a, NULL, 0, 0))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is going to contain a normalized form of a, use a->length() as the 3rd arg for the initial size.

The failure of this is a memory allocation failure. While it hasn't been done well in other examples in this file yet, the response is:

my_error(ER_OUTOFMEMORY, MYF(0), a->length());
goto return_null

As a pushed error it shouldn't return here

Comment thread sql/item_jsonfunc.cc
}
else
{
cached_a.length= 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a comment in code here about resetting string for next value.

Comment thread sql/item_jsonfunc.cc
DYNAMIC_STRING b_res;
if (init_dynamic_string(&b_res, NULL, 0, 0))
/* Process First Argument */
if (a_const && a_parsed)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a_parsed is always the same as cached_a.str != nullptr so I think a_parsed can be eliminated.

Comment thread sql/item_jsonfunc.cc
goto return_null;
if (a_const)
{
a_null= false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think can set a_null unconditionally. Its only looked at under a_const ==true anyway0.

Comment thread sql/item_jsonfunc.cc

result= strcmp(a_res.str, b_res.str) ? 0 : 1;
/* Process Second Argument */
if (b_const && b_parsed)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comments on first arg parsing apply here too.

Comment thread sql/item_jsonfunc.cc
String *v= args[1]->val_json(&tmp_val);
if (v)
{
cached_val.copy(v->ptr(), v->length(), v->charset());

@grooverdan grooverdan Jul 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still copying here.

if (b_const)
{
   if (cached_val.is_allocated())
      val= &cached_val; /* or something*/
   else
   {
      val= args[1]->val_json(&cached_val);
   }
}
else
{
  something using local val= tmp_str (as local)
}

(incomplete)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants